export type Reviver = (key: unknown, value: unknown) => unknown

/**
 * Applies the JSON.parse reviver algorithm as defined in the ECMA-262 spec,
 * in section 24.5.1.1 "Runtime Semantics: InternalizeJSONProperty" of the
 * 2021 edition: https://tc39.es/ecma262/#sec-json.parse
 *
 * Includes extensions for handling Map and Set objects.
 */
export function applyReviver(
  reviver: Reviver,
  obj: unknown,
  key: unknown,
  val: any
) {
  if (val && typeof val === 'object') {
    if (Array.isArray(val)) {
      for (let i = 0, len = val.length; i < len; ++i) {
        const v0 = val[i]
        const v1 = applyReviver(reviver, val, String(i), v0)
        // eslint-disable-next-line @typescript-eslint/no-array-delete
        if (v1 === undefined) delete val[i]
        else if (v1 !== v0) val[i] = v1
      }
    } else if (val instanceof Map) {
      for (const k of Array.from(val.keys())) {
        const v0 = val.get(k)
        const v1 = applyReviver(reviver, val, k, v0)
        if (v1 === undefined) val.delete(k)
        else if (v1 !== v0) val.set(k, v1)
      }
    } else if (val instanceof Set) {
      for (const v0 of Array.from(val)) {
        const v1 = applyReviver(reviver, val, v0, v0)
        if (v1 === undefined) val.delete(v0)
        else if (v1 !== v0) {
          val.delete(v0)
          val.add(v1)
        }
      }
    } else {
      for (const [k, v0] of Object.entries(val)) {
        const v1 = applyReviver(reviver, val, k, v0)
        if (v1 === undefined) delete val[k]
        else if (v1 !== v0) val[k] = v1
      }
    }
  }
  return reviver.call(obj, key, val)
}
